package away3d.core.render ;


import away3d.core.data.RenderableListItem ;
import away3d.core.managers.Stage3DProxy ;
import away3d.core.sort.EntitySorterBase ;
import away3d.core.sort.RenderableMergeSort ;
import away3d.core.traverse.EntityCollector ;
import away3d.errors.AbstractMethodError ;
import flash.display3D.Context3D ;
import flash.display3D.textures.TextureBase ;
import flash.events.Event ;
import flash.geom.Rectangle ;



/**
 * RendererBase forms an abstract base class for classes that are used in the rendering pipeline to render geometry
 * to the back buffer or a texture.
 */
class RendererBase
{
    
    
    private var _context                : Context3D ;
    private var _stage3DProxy           : Stage3DProxy ;
    private var _contextIndex           : Int ;
    private var _backBufferWidth        : Int  ;
    private var _backBufferHeight       : Int  ;
    private var _backBufferInvalid      : Bool ;
    private var _antiAlias              : Int  ;
    private var _renderMode             : String ;
    private var _backgroundR            : Float ;
    private var _backgroundG            : Float ;
    private var _backgroundB            : Float ;
    private  var _viewPortWidth         : Float ;
    private  var _viewPortHeight        : Float ;
    private  var _viewPortX             : Float ;
    private  var _viewPortY             : Float ;
    private var _viewPortInvalid        : Bool ;
    private  var _enableDepthAndStencil : Bool ;
    private  var _swapBackBuffer        : Bool ;
    private var _renderableSorter       : EntitySorterBase ;
    
    
    
    /**
     * Creates a new RendererBase object.
     * @param renderBlended Indicates whether semi-transparent objects should be rendered.
     * @param antiAlias The amount of anti-aliasing to be used.
     * @param renderMode The render mode to be used.
     */
    public function new (   antiAlias               : Int       = 0
                        ,   enableDepthAndStencil   : Bool      = true
                        ,   renderMode              : String    = "auto"
                        )
    {
        
        _contextIndex           = -1 ;
        _backgroundR            = 0 ;
        _backgroundG            = 0 ;
        _backgroundB            = 0 ;
        _viewPortWidth          = 1 ;
        _viewPortHeight         = 1 ;
        _viewPortX              = 0 ;
        _viewPortY              = 0 ;
        _swapBackBuffer         = true ;
        _antiAlias              = antiAlias ;
        _renderMode             = renderMode ;
        _enableDepthAndStencil  = enableDepthAndStencil ;
        _renderableSorter       = new RenderableMergeSort() ;
        
    }
    
    
    public var renderableSorter( get_renderableSorter, set_renderableSorter ) : EntitySorterBase ;
    
    
    private function get_renderableSorter() : EntitySorterBase
    {
        
        return _renderableSorter ;
        
    }
    
    
    private function set_renderableSorter( value : EntitySorterBase ) : EntitySorterBase 
    {
        
        _renderableSorter = value ;
        return value ;
        
    }
    
    
    public var context( get_context, null ) : Context3D ;
    
    
    private function get_context() : Context3D
    {
        
        return _context ;
        
    }
    
    
    public var contextIndex( get_contextIndex, null ) : Int ;
    
    
    private function get_contextIndex() : Int
    {
        
        return _contextIndex ;
        
    }
    
    
    public var swapBackBuffer( get_swapBackBuffer, set_swapBackBuffer ) : Bool ;
    
    
    /**
     * Indicates whether or not the back buffer should be swapped when rendering is complete.
     */
    private function get_swapBackBuffer() : Bool
    {
        
        return _swapBackBuffer ;
        
    }
    
    
    private function set_swapBackBuffer( value : Bool )
    {
        
        _swapBackBuffer = value ;
        
    }
    
    
    public var antiAlias( get_antiAlias, set_antiAlias ) : Int ;
    
    
    /**
     * The amount of anti-aliasing to use.
     */
    private function get_antiAlias() : Int
    {
        
        return _antiAlias ;
        
    }
    
    
    private function set_antiAlias( value : Int ) : Int 
    {
        
        _backBufferInvalid  = true ;
        _antiAlias          = value ;
        return value ;
        
    }
    
    
    public var backgroundR( get_backgroundR, set_backgroundR ) : Float ;
    
    
    /**
     * The background color's red component, used when clearing.
     *
     * @private
     */
    private function get_backgroundR() : Float
    {
        
        return _backgroundR ;
        
    }
    
    
    private function set_backgroundR( value : Float ) : Float
    {
        
        _backgroundR = value ;
        return value ;
        
    }
    
    
    public var backgroundG( get_backgroundG, set_backgroundG ) : Float ;
    
    
    /**
     * The background color's green component, used when clearing.
     *
     * @private
     */
    private function get_backgroundG() : Float
    {
        
        return _backgroundG ;
        
    }
    
    
    private function set_backgroundG( value : Float )
    {
        
        _backgroundG = value ;
        return value ;
        
    }
    
    
    public var backgroundB( get_backgroundB, set_backgroundB ) : Float ;
    
    
    /**
     * The background color's blue component, used when clearing.
     *
     * @private
     */
    private function get_backgroundB() : Float
    {
        
        return _backgroundB ;
        
    }
    
    
    private function set_backgroundB( value : Float )
    {
        
        _backgroundB = value ;
        return value ;
        
    }
    
    
    public var stage3DProxy( get_stage3DProxy, set_stage3DProxy ): Stage3DProxy ;
    
    
    /**
     * The Stage3DProxy that will provide the Context3D used for rendering.
     *
     * @private
     */
    private function get_stage3DProxy() : Stage3DProxy
    {
        
        return _stage3DProxy ;
        
    }
    
    private function set_stage3DProxy( value : Stage3DProxy ) : Stage3DProxy
    {
        
        if( value == _stage3DProxy ) return value;
        
        if( !value )
        {
            
            if( _stage3DProxy ) _stage3DProxy.removeEventListener( Event.CONTEXT3D_CREATE, onContextUpdate ) ;
            
            _stage3DProxy   = null ;
            _context        = null ;
            _contextIndex   = -1 ;
            
            return null ;
            
        }
        else if( _stage3DProxy ) throw new Error( "A Stage3D instance was already assigned!" ) ;
        
        _stage3DProxy = value ;
        updateViewPort() ;
        
        if( value.context3D )
        {
            _context = value.context3D ;
            _contextIndex = value.stage3DIndex ;
        }
        else
        {
            value.addEventListener( Event.CONTEXT3D_CREATE, onContextUpdate ) ;
        }
        
        return _stage3DProxy ;
        
    }
    
    
    public var backBufferWidth( get_backBufferWidth, set_backBufferWidth ) : Int ;
    
    
    /**
     * The width of the back buffer.
     *
     * @private
     */
    private function get_backBufferWidth() : Int
    {
        
        return _backBufferWidth ;
        
    }
    
    
    private function set_backBufferWidth( value : Int )
    {
        
        _backBufferWidth = value ;
        _backBufferInvalid = true ;
        return value ;
        
    }
    
    
    public var backBufferHeight( get_backBufferHeight, set_backBufferHeight ) : Int ;
    
    
    /**
     * The height of the back buffer.
     *
     * @private
     */
    private function get_backBufferHeight() : int
    {
        
        return _backBufferHeight ;
        
    }
    
    private function set_backBufferHeight( value : int )
    {
        
        _backBufferHeight = value ;
        _backBufferInvalid = true ;
        return value ;
        
    }
    
    
    public var viewPortX( get_viewPortX, set_viewPortX ) : Float ;
    
    
    /**
     * The horizontal coordinate of the top-left corner of the viewport.
     *
     * @private
     */
    private function get_viewPortX() : Float
    {
        
        return _viewPortX ;
        
    }
    
    
    private function set_viewPortX( value : Float ) : Float
    {
        
        _viewPortX = value ;
        _viewPortInvalid = true ;
        return value ;
        
    }
    
    
    public var viewPortY( get_viewPortY, set_viewPortY ): Float ;
    
    
    /**
     * The vertical coordinate of the top-left corner of the viewport.
     *
     * @private
     */
    private function get_viewPortY() : Float
    {
        
        return _viewPortY ;
        
    }
    
    
    private function set_viewPortY( value : Float ) : Float 
    {
        
        _viewPortY          = value ;
        _viewPortInvalid    = true ;
        return value ;
        
    }
    
    
    public var viewPortWidth( get_viewPortWidth, set_viewPortWidth ): Float ;
    
    
    /**
     * The width of the viewport.
     *
     * @private
     */
    private function get_viewPortWidth() : Float
    {
        
        return _viewPortWidth ;
        
    }
    
    
    private function set_viewPortWidth( value : Float ) : Float
    {
        
        _viewPortWidth = value ;
        _viewPortInvalid = true ;
        return value ;
        
    }
    
    
    private var viewPortHeight( get_viewPortHeight, set_viewPortHeight ) : Float ;
    
    
    /**
     * The height of the viewport.
     *
     * @private
     */
    private function get_viewPortHeight() : Float
    {
        
        return _viewPortHeight ;
        
    }
    
    
    private function set_viewPortHeight( value : Float ) : Float
    {
        
        _viewPortHeight = value ;
        _viewPortInvalid = true ;
        return value ;
        
    }
    
    
    /**
     * Disposes the resources used by the RendererBase.
     *
     * @private
     */
    public function dispose()
    {
        stage3DProxy = null ;
    }

    /**
     * Renders the potentially visible geometry to the back buffer or texture.
     * @param entityCollector The EntityCollector object containing the potentially visible geometry.
     * @param target An option target texture to render to.
     * @param surfaceSelector The index of a CubeTexture's face to render to.
     * @param additionalClearMask Additional clear mask information, in case extra clear channels are to be omitted.
     */
    public function render  (   entityCollector         : EntityCollector
                            ,   ?target                 : TextureBase       = null
                            ,   ?surfaceSelector        : Int               = 0
                            ,   ?additionalClearMask    : Int               = 7
                            )
    {
        
        if( !_stage3DProxy ) return ;
        
        if( _viewPortInvalid ) updateViewPort() ;
        
        if( _backBufferInvalid ) updateBackBuffer() ;
        
        if( !_context ) return ;
        
        executeRender( entityCollector, target, surfaceSelector, additionalClearMask ) ;
        
    }

    /**
     * Renders the potentially visible geometry to the back buffer or texture. Only executed if everything is set up.
     * @param entityCollector The EntityCollector object containing the potentially visible geometry.
     * @param target An option target texture to render to.
     * @param surfaceSelector The index of a CubeTexture's face to render to.
     * @param additionalClearMask Additional clear mask information, in case extra clear channels are to be omitted.
     */
    private function executeRender  (   entityCollector         : EntityCollector
                                    ,   ?target                 : TextureBase       = null
                                    ,   ?surfaceSelector        : Int               = 0
                                    ,   ?additionalClearMask    : Int               = 7 
                                    )
    {
        
        _renderableSorter.sort( entityCollector ) ;
        
        if( target )
        {
            
            _context.setRenderToTexture( target, _enableDepthAndStencil, _antiAlias, surfaceSelector ) ;
            
        }
        else
        {
            
            _context.setRenderToBackBuffer() ;
            
        }
        
        _context.clear( _backgroundR, _backgroundG, _backgroundB, 1, 1, 0, additionalClearMask ) ;
        
        draw( entityCollector ) ;
        
        if( _swapBackBuffer && !target ) _context.present() ;
        
    }
    
    
    /**
     * Performs the actual drawing of geometry to the target.
     * @param entityCollector The EntityCollector object containing the potentially visible geometry.
     */
    private  function draw( entityCollector : EntityCollector )
    {
        
        throw new AbstractMethodError() ;
        
    }
    
    
    /**
     * Updates the viewport dimensions ;
     */
    private  function updateViewPort()
    {
        
        _stage3DProxy.viewPort  = new Rectangle( _viewPortX, _viewPortY, _viewPortWidth, _viewPortHeight ) ;
        _viewPortInvalid        = false ;
        
    }
    
    
    /**
     * Updates the backbuffer dimensions.
     */
    private function updateBackBuffer()
    {
        
        _stage3DProxy.configureBackBuffer( _backBufferWidth, _backBufferHeight, _antiAlias, _enableDepthAndStencil ) ;
        _backBufferInvalid = false ;
        
    }
    
    
    /**
     * Assign the context once retrieved
     */
    private function onContextUpdate( event : Event )
    {
        
        _context        = _stage3DProxy.context3D ;
        _contextIndex   = _stage3DProxy.stage3DIndex ;
        
    }
    
    
}


}